import { Callout } from 'nextra/components'
import BadgeGroup, { UniverTypes } from '@/components/BadgeGroup'
import ReactLive from '@/components/ReactLive'
import ServerNotice from '@/components/ServerNotice'

# Univer Sheet API

<BadgeGroup values={[UniverTypes.SHEET]} value={UniverTypes.SHEET} />

## 概念

Univer 表格相关概念会尽可能与 Excel 保持一致。

## 工作簿 Workbook

一个工作簿里包含多个工作表，可以看作是一个 Excel 文件。

`unitId` 可用作工作簿的唯一标识。

### 创建工作簿

`univer.createUnit(UniverInstanceType.UNIVER_SHEET, {})` 方法会创建并返回 `Workbook` 对象。

export const code = `const univer = new Univer({
  theme: defaultTheme,
  locale: LocaleType.ZH_CN,
  locales: {
    [LocaleType.ZH_CN]: zhCN,
  },
});

univer.registerPlugin(UniverRenderEnginePlugin);
univer.registerPlugin(UniverFormulaEnginePlugin);
univer.registerPlugin(UniverUIPlugin, {
  container: 'app',
});

univer.registerPlugin(UniverDocsPlugin, {
  hasScroll: false,
});
univer.registerPlugin(UniverDocsUIPlugin);

univer.registerPlugin(UniverSheetsPlugin);
univer.registerPlugin(UniverSheetsUIPlugin);
univer.registerPlugin(UniverSheetsFormulaPlugin);

// Passing in an empty object will automatically initialize the workbook
univer.createUnit(UniverInstanceType.UNIVER_SHEET, {});`

<ReactLive code={code}  />

### 获取工作簿数据

```typescript
const univerAPI = FUniver.newAPI(univer);
const activeWorkbook = univerAPI.getActiveWorkbook()
const saveData = activeWorkbook.getSnapshot();
```

### 销毁工作簿

当我们不再需要工作簿时，可以调用 `FUniver` 的 `disposeUniverSheet` 方法来销毁实例。

```typescript
univerAPI.disposeUnit('your-sheet-id');
```

## 工作表 Worksheet

工作表中存储着表格数据，工作表属于工作薄，

一个工作薄可以包含多个工作表，同一个工作薄中工作表的名称不能重复。

`subUnitId` 可用作在工作薄中工作表的唯一标识。

### 获取工作表

获取工作薄中所有工作表
```typescript
const activeWorkbook = univerAPI.getActiveWorkbook();
const sheets = activeWorkbook.getSnapshot().sheets;
```

获取工作薄中当前激活的工作表

```typescript
const activeWorkbook = univerAPI.getActiveWorkbook();
const activeSheet = activeWorkbook.getActiveSheet();
```

### 获取工作表数据

```typescript
const activeWorkbook = univerAPI.getActiveWorkbook();
const snapshot = activeWorkbook.getSnapshot()
const sheet1 = Object.values(snapshot.sheets).find((sheet) => {
  return sheet.name === 'Sheet1'
})
```

### 创建工作表

在创建工作薄时，如果不传入参数，会自动创建一个工作表。

下面例子展示通过 `Workbook.create` 方法创建一个工作表。

export const code2 = `const univer = new Univer({
  theme: defaultTheme,
  locale: LocaleType.ZH_CN,
  locales: {
    [LocaleType.ZH_CN]: zhCN,
  },
});

univer.registerPlugin(UniverRenderEnginePlugin);
univer.registerPlugin(UniverFormulaEnginePlugin);
univer.registerPlugin(UniverUIPlugin, {
  container: 'app',
});

univer.registerPlugin(UniverDocsPlugin, {
  hasScroll: false,
});
univer.registerPlugin(UniverDocsUIPlugin);

univer.registerPlugin(UniverSheetsPlugin);
univer.registerPlugin(UniverSheetsUIPlugin);
univer.registerPlugin(UniverSheetsFormulaPlugin);

// Passing in an empty object will automatically initialize the workbook
univer.createUnit(UniverInstanceType.UNIVER_SHEET, {})

const univerAPI = FUniver.newAPI(univer);
//const activeWorkbook = univerAPI.getActiveWorkbook();

// Create a new sheet that named 'Sheet2' with 10 rows and 10 columns
//const sheet = activeWorkbook.create('Sheet2', 10, 10)`

<ReactLive code={code2}  />

### 删除工作表

删除工作表需要知道工作表的 Id。

```typescript
const sheetId = 'SheetId';
univerAPI.executeCommand('sheet.command.remove-sheet', { subUnitId: sheetId });
```

### 激活工作表

激活工作表需要知道工作薄的 Id 和工作表的 Id。

```typescript
const workbookId = 'WorkbookId';
const sheetId = 'SheetId';
univerAPI.executeCommand('sheet.command.active-sheet', { unitId: workbookId, subUnitId: sheetId });
```

## 单元格 Cell

单元格数据以二维 Map 的形式存储在工作表中，一二级索引分别代表行号和列号。

以下是一个典型的单元格对象：

```typescript
{
  v: 'Hello, Univer',
  s: 'styleId',
  t: CellValueType.STRING
}
```

详细的字段说明请参考 [单元格信息](/guides/sheet/getting-started/cell-data)。

<Callout type="info" emoji="ℹ️">
  对单元格的操作可以看作对行高 1 、列宽 1 的范围进行操作，操作范围请阅读 [范围-range](/guides/sheet/facade/sheet-api#范围-range)。
</Callout>

<Callout type="info" emoji="ℹ️">
  插件还会将拓展的单元格属性存储在 `Workbook` 的 `resources` 属性中，详细请阅读 [插件自定义模型](/guides/sheet/advanced/custom-model/)。
</Callout>

### 单元格 PointerMove

`onCellPointerMove` 事件在鼠标更改坐标时触发
```typescript
univerAPI.getSheetHooks().onCellPointerMove((cell) => {
  // 拿到当前鼠标指向的单元格
  console.log(cell);
})
```

### 单元格 PointerOver

`onCellPointerOver` 事件在鼠标移动到单元格的边界时触发
```typescript
univerAPI.getSheetHooks().onCellPointerOver((cell) => {
  // 拿到当前鼠标指向的单元格
  console.log(cell);
})
```

### 单元格 DragOver

`onCellDragOver` 事件在拖动元素或文本到单元格的边界时触发
```typescript
univerAPI.getSheetHooks().onCellDragOver((cell) => {
  // 拿到当前鼠标指向的单元格
  console.log(cell);
})
```

### 单元格 Drop

`onCellDrop` 事件在拖动元素或文本到单元格上释放时触发
```typescript
univerAPI.getSheetHooks().onCellDrop((cell) => {
  // 拿到当前鼠标指向的单元格
  console.log(cell);
})
```

### 单元格进入编辑

```typescript
univerAPI.onCommandExecuted((command) => {
  if(command.id === 'sheet.operation.set-cell-edit-visible' && command.params.visible){
    console.log('Cell edit visible')
  }
})
```

### 单元格退出编辑

```typescript
univerAPI.onCommandExecuted((command) => {
  if(command.id === 'sheet.operation.set-cell-edit-visible' && !command.params.visible){
    console.log('Cell edit invisible')
  }
})
```

## 范围 Range

范围指工作表中的一块矩形区域，有起始行号、起始列号、长宽或者结束行号、结束列号来确定。

### 创建范围

获得一个范围需要知道起始行号、起始列号、长宽。

创建 A1 单元格的范围：

```typescript
const activeSheet = univerAPI.getActiveWorkbook().getActiveSheet();
const range = activeSheet.getRange(0, 0, 1, 1);
```

创建 A1:B2 的范围：

```typescript
const activeSheet = univerAPI.getActiveWorkbook().getActiveSheet();
const range = activeSheet.getRange(0, 0, 2, 2);
```

### 获取范围数据

获取范围第一个单元格的值

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
const value = range.getValue();
```

获取范围的所有值

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
range.forEach((cell, row, column) => {
  console.log(cell.getValue());
});
```

获取范围的所有公式

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
console.log(range.getFormulas());
```

### 设置范围数据

#### 设置单一值

传入一个值或者单元格对象，将会覆盖范围内所有单元格，如果以 `=` 开头，将被解释为公式。

比如，设置 A1:B2 的值为 `Hello, Univer`：
```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
range.setValue('Hello, Univer');
```

设置 A1+B1 的值为公式：
```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
range.setValue('=A1+B1');
```

设置 A1:B2 的值为单元格对象：
```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
range.setValue({
  v: 'Hello, Univer',
  custom: {
      key: 'value',
  },
});
```

#### 通过数组设置多个值

数组的长度和宽度必须和范围的长宽一致。

可以传入单元格值也可以传入单元格对象。

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
range.setValues([
  ['A1', 'B1'],
  ['A2', 'B2'],
]);

range.setValues([
  [{ v: 'A1' }, { v: 'B1' }],
  [{ v: 'A2' }, { v: 'B2' }],
]);
```

#### 通过对象设置多个值

则对象的一级索引代表行号，二级索引代表列号，与范围的长宽无需一致。

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
range.setValues({
  0: {
    0: 'A1',
    1: 'B1',
  },
  1: {
    0: 'A2',
    1: 'B2',
  },
});
```

### 获取范围样式

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
const style = range.getCellStyleData();
```

### 设置范围样式

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
range
  .setFontWeight('bold')
  .setFontLine('underline')
  .setFontFamily('Arial')
  .setFontSize(24)
  .setFontColor('red');
```

### 清理范围样式

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
range
  .setFontWeight(null)
  .setFontLine(null)
  .setFontFamily(null)
  .setFontSize(null)
  .setFontColor(null);
```

### 是否合并

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
const isMerged = range.isMerged();
```

### 获取坐标

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
const rect = range.getCellRect(); // width、heigh、left、right、top、bottom、x、y
```

### 同时获取范围的合并信息和坐标

```typescript
const range = activeSheet.getRange(0, 0, 2, 2);
const cell = range.getCell();
```

## 选区 Selection

Univer 表格支持多选区，所以选区是一个范围数组，可以通过范围 API 来操作选区数据。

我们还提供 API 来获取当前选区、设置选区和监听选区变化。

### 获取激活选区的范围

```typescript
const activeSheet = univerAPI.getActiveWorkbook().getActiveSheet();
const selection = activeSheet.getSelection();
const range = selection.getActiveRange();
```

### 设置选区

设置 A1:B2 为选区

```typescript
const sheetId = 'SheetId';
univerAPI.executeCommand('sheet.operation.set-selections', {
  selections: [{
    range: {
      startRow: 0,
      startColumn: 0,
      endRow: 1,
      endColumn: 1,
      rangeType: 0,
    },
  }],
  subUnitId,
  unitId: activeWorkbook.getId(),
  type: 2,
})
```

### 监听选区变化

```typescript
const activeWorkbook = univerAPI.getActiveWorkbook();
activeWorkbook.onSelectionChange((selection) => {
  console.log(selection);
});
```

## 与服务端交互的功能

<ServerNotice />

### 导入 XLSX

导入需要使用[服务端版本 Facade API](/zh-CN/guides/sheet/facade/facade#与服务端配合使用)。

#### 导入 XLSX 并获取 `unitId`

在协同环境下，每个工作簿都有一个唯一的 `unitId`。使用 API `importXLSXToUnitId` 传入 `file` 参数会返回 `unitId`，可以通过该 `unitId` 来访问工作簿。 `file` 参数可以是一个 File 对象，或者远程文件的 URL。

```typescript
univerAPI.importXLSXToUnitId(file).then((id)=>{
  console.log(id)
})
```

#### 导入 XLSX 并获取 Workbook 数据

非协同环境中单独使用导入 XLSX 能力，可使用 API `importXLSXToSnapshot` 返回 IWorkbookData 格式的工作簿数据。

```typescript
univerAPI.importXLSXToSnapshot(file).then((data)=>{
  console.log(data)
})
```

### 导出 XLSX

导出需要使用[服务端版本 Facade API](/zh-CN/guides/sheet/facade/facade#与服务端配合使用)。

### 通过 `unitId` 导出 XLSX

使用 API `exportXLSXByUnitId` 传入 `unitId` 参数，返回一个 File 对象。

```typescript
univerAPI.exportXLSXByUnitId(unitId).then((file)=>{
  console.log(file)
})
```

### 通过 Workbook 数据导出 XLSX

使用 API `exportXLSXBySnapshot` 传入 `IWorkbookData` 格式的表格数据, 返回一个 File 对象。

可参考[获取工作簿数据](/guides/sheet/facade/sheet-api#获取工作簿数据)获取 `IWorkbookData` 格式的数据。

```typescript
univerAPI.exportXLSXBySnapshot(snapshot).then((file)=>{
  console.log(file)
})
```

## 参考

更多 API 请查看：

- [Facade API](/typedoc/@univerjs/facade/README)
